﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SSharp.Core.DataTypes;
using SSharp.Core.Evaluator;
using System.Numerics;

namespace SSharp.Core.Builtins.Functions {
	public static class PrimitiveMath {
		public static readonly PrimitiveProcedure Plus = new PrimitiveProcedure("+",
			args => {
				dynamic result = 0;
				foreach (dynamic arg in args) {
					result += arg;
				}
				return result;
			});

		public static readonly PrimitiveProcedure Minus = new PrimitiveProcedure("-",
			args => {
				if (args.Length == 0) {
					throw new ApplicationError("- expects one or more arguments, given 0");
				} else if (args.Length == 1) {
					return -((dynamic)args[0]);
				} else {
					dynamic result = args[0];
					foreach (dynamic arg in args.Skip(1)) {
						result -= arg;
					}
					return result;
				}
			});

		public static readonly PrimitiveProcedure Times = new PrimitiveProcedure("*",
			args => {
				if (args.Length < 2) {
					throw new ApplicationError("* expects two or more arguments, given " + args.Length);
				} else {
					dynamic result = args[0];
					foreach (dynamic arg in args.Skip(1)) {
						result *= arg;
					}
					return result;
				}
			});

		public static readonly PrimitiveProcedure Divide = new PrimitiveProcedure("/",
			args => {
				if (args.Length < 2) {
					throw new ApplicationError("/ expects two or more arguments, given " + args.Length);
				} else {
					dynamic result = args[0];
					foreach (dynamic arg in args.Skip(1)) {
						// since BigInteger doesn't have a division operator defined against decimal, we must define it ourselves.
						if (((result is BigInteger) && (arg is decimal))
							|| ((result is decimal) && (arg is BigInteger))) {
							// one is a decimal, the other is a BigInteger; convert both to decimal
							decimal dArg = (decimal)arg;
							result = ((decimal)result) / dArg;
						} else {
							// normal division
							result /= arg;
						}
					}
					return result;
				}
			});

		public static readonly PrimitiveProcedure Remainder = new PrimitiveProcedure("remainder",
			args => {
				if (args.Length != 2) {
					throw new ApplicationError("remainder expects two arguments, given " + args.Length);
				} else {
					return
						((dynamic)args[0])
						%
						((dynamic)args[1]);
				}
			});

		public static readonly PrimitiveProcedure LessThan = new PrimitiveProcedure("<", args => {
			if (args.Length != 2) {
				throw new ApplicationError("< expects two arguments, given " + args.Length);
			} else {
				return
					((dynamic)args[0])
					<
					((dynamic)args[1]);
			}
		});

		public static readonly PrimitiveProcedure MoreThan = new PrimitiveProcedure(">", args => {
			if (args.Length != 2) {
				throw new ApplicationError("> expects two arguments, given " + args.Length);
			} else {
				return
					((dynamic)args[0])
					>
					((dynamic)args[1]);
			}
		});

		public static readonly PrimitiveProcedure LessThanOrEqual = new PrimitiveProcedure("<=", args => {
			if (args.Length != 2) {
				throw new ApplicationError("<= expects two arguments, given " + args.Length);
			} else {
				return
					((dynamic)args[0])
					<=
					((dynamic)args[1]);
			}
		});

		public static readonly PrimitiveProcedure MoreThanOrEqual = new PrimitiveProcedure(">=", args => {
			if (args.Length != 2) {
				throw new ApplicationError(">= expects two arguments, given " + args.Length);
			} else {
				return
					((dynamic)args[0])
					>=
					((dynamic)args[1]);
			}
		});

		public static readonly PrimitiveProcedure Eq = new PrimitiveProcedure("eq?", args => {
			if (args.Length != 2) {
				throw new ApplicationError(">= expects two arguments, given " + args.Length);
			} else {
				return ReferenceEquals(args[0], args[1]);
			}
		});

		public new static readonly PrimitiveProcedure Equals = new PrimitiveProcedure("=", args => {
			if (args.Length != 2) {
				throw new ApplicationError(">= expects two arguments, given " + args.Length);
			} else {
				// dynamic dispatch is important here
				return ((dynamic)args[0])
					==
					((dynamic)args[1]);
			}
		});
	}
}